package framework

import (
	"fmt"
	"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
	"reflect"
	"strings"
	"time"
)

// NewCollectorState creates a new collector state
func NewCollectorState() *CollectorState {
	return &CollectorState{
		instances:         make(map[string]*Instance),
		obsoleteInstances: make([]string, 0),
		metrics:           make([]MetricValue, 0, 1000),
		errors:            make(map[string]error),
		protocols:         make(map[string]*ProtocolMetrics),
	}
}

// GetInstance returns or creates an instance for the given context and labels
func (s *CollectorState) GetInstance(ctx interface{}, labels interface{}) *Instance {
	// Extract context metadata including label order
	contextMeta := extractContextMetadata(ctx)
	if contextMeta == nil {
		return nil
	}

	// Extract context name
	contextName := contextMeta.Name

	// Generate instance key
	var key string
	var labelMap map[string]string

	if labels != nil {
		// Always convert to map for storage
		labelMap = structToMap(labels)

		// Use the InstanceID method if available
		if labeler, ok := labels.(interface{ InstanceID(string) string }); ok {
			key = labeler.InstanceID(contextName)
		} else {
			// Fallback to manual generation
			key = generateInstanceKeyWithOrder(contextName, contextMeta.LabelOrder, labelMap)
		}
	} else {
		// No labels, just use context name
		key = contextName
		labelMap = make(map[string]string)
	}

	// Get or create instance
	instance, exists := s.instances[key]
	if !exists {
		instance = &Instance{
			key:         key,
			contextName: contextName,
			labels:      labelMap,
			lastSeen:    time.Now(),
		}
		s.instances[key] = instance
	}

	instance.lastSeen = time.Now()
	return instance
}

// SetMetricsForGeneratedCode is ONLY for use by code generated by metricgen.
// DO NOT call this method directly in hand-written code.
// Use the type-safe Set() methods on generated context types instead.
func (s *CollectorState) SetMetricsForGeneratedCode(ctx interface{}, labels interface{}, values map[string]int64) {
	s.set(ctx, labels, values)
}

// SetUpdateEveryOverrideForGeneratedCode is ONLY for use by code generated by metricgen.
// DO NOT call this method directly in hand-written code.
// Use the type-safe SetUpdateEvery() methods on generated context types instead.
func (s *CollectorState) SetUpdateEveryOverrideForGeneratedCode(ctx interface{}, labels interface{}, updateEvery int) {
	instance := s.GetInstance(ctx, labels)
	if instance != nil {
		instance.UpdateEveryOverride = updateEvery
	}
}

// set stores multiple metric values for an instance
// This method is unexported to prevent direct usage by modules.
// Use the type-safe Set() methods on generated context types instead.
func (s *CollectorState) set(ctx interface{}, labels interface{}, values map[string]int64) {
	instance := s.GetInstance(ctx, labels)

	// Get context metadata
	contextMeta := getContextMetadata(ctx)

	// Store each metric value
	for dimName, rawValue := range values {
		// Find dimension metadata
		var dim *Dimension
		for _, d := range contextMeta.Dimensions {
			if d.Name == dimName {
				dim = &d
				break
			}
		}

		if dim == nil {
			// Log warning for unknown dimensions to help catch typos and configuration errors
			s.Warningf("unknown dimension '%s' for context '%s' - dimension will be skipped", dimName, contextMeta.Name)
			continue
		}

		// Apply precision FIRST, then unit conversion to avoid integer division precision loss
		// 1. First apply precision multiplication to preserve accuracy
		// 2. Then apply unit conversion: (value * mul) / div
		// This ensures accurate conversion to base units
		precisionValue := rawValue * int64(dim.Precision)
		finalValue := precisionValue
		if dim.Mul != 0 && dim.Div != 0 {
			finalValue = (precisionValue * int64(dim.Mul)) / int64(dim.Div)
		}

		s.metrics = append(s.metrics, MetricValue{
			Instance:  *instance,
			Dimension: dimName,
			Value:     finalValue,
			Timestamp: time.Now(),
		})
	}
}

// IsTimeFor checks if it's time to collect a specific context
func (s *CollectorState) IsTimeFor(contextName string) bool {
	// Get context metadata to find update interval
	interval := getContextUpdateInterval(contextName)
	if interval <= 1 {
		return true // Collect every iteration
	}

	// Check if current iteration is a multiple of the interval
	return s.iteration%int64(interval) == 0
}

// TrackError records an error for a specific object
func (s *CollectorState) TrackError(objectType, objectName string, err error) {
	key := fmt.Sprintf("%s:%s", objectType, objectName)
	s.errors[key] = err
}

// ClearErrors removes all tracked errors
func (s *CollectorState) ClearErrors() {
	s.errors = make(map[string]error)
}

// NextIteration handles obsoletion and clears metrics for next iteration
func (s *CollectorState) NextIteration(obsoletionTimeout int) {
	// Note: iteration counter is now incremented in Collect()

	// Clear metrics for next iteration
	s.metrics = s.metrics[:0]

	// Clear previous obsolete instances list
	s.obsoleteInstances = s.obsoleteInstances[:0]

	// Check for obsolete instances
	cutoff := time.Now().Add(-time.Duration(obsoletionTimeout) * time.Second)
	for key, instance := range s.instances {
		if instance.lastSeen.Before(cutoff) {
			// Track as obsolete for chart cleanup
			s.obsoleteInstances = append(s.obsoleteInstances, key)
			delete(s.instances, key)
		}
	}
}

// GetMetrics returns all collected metrics for the current iteration
func (s *CollectorState) GetMetrics() []MetricValue {
	return s.metrics
}

// RegisterProtocol registers a protocol for automatic observability
func (s *CollectorState) RegisterProtocol(name string) *ProtocolMetrics {
	pm := &ProtocolMetrics{Name: name}
	s.protocols[name] = pm
	return pm
}

// GetObsoleteInstances returns instances that became obsolete in this iteration
func (s *CollectorState) GetObsoleteInstances() []string {
	return s.obsoleteInstances
}

// GetIteration returns the current global iteration counter
func (s *CollectorState) GetIteration() int64 {
	return s.iteration
}

// Helper functions

func extractContextName(ctx interface{}) string {
	// Use reflection to get the Name field from Context[T]
	v := reflect.ValueOf(ctx)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return ""
	}

	nameField := v.FieldByName("Name")
	if nameField.IsValid() && nameField.Kind() == reflect.String {
		return nameField.String()
	}

	return ""
}

func structToMap(labels interface{}) map[string]string {
	result := make(map[string]string)

	if labels == nil {
		return result
	}

	// Use reflection to extract struct fields
	v := reflect.ValueOf(labels)
	t := reflect.TypeOf(labels)

	// Handle pointer types
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return result
		}
		v = v.Elem()
		t = t.Elem()
	}

	// Must be a struct
	if v.Kind() != reflect.Struct {
		return result
	}

	// Extract all exported fields from the struct
	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		fieldType := t.Field(i)

		// Skip unexported fields
		if !fieldType.IsExported() {
			continue
		}

		// Convert field name to lowercase for consistency
		key := strings.ToLower(fieldType.Name)

		// Convert field value to string
		var value string
		switch field.Kind() {
		case reflect.String:
			value = field.String()
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			value = fmt.Sprintf("%d", field.Int())
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			value = fmt.Sprintf("%d", field.Uint())
		case reflect.Float32, reflect.Float64:
			value = fmt.Sprintf("%.3f", field.Float())
		case reflect.Bool:
			value = fmt.Sprintf("%t", field.Bool())
		default:
			// For other types, use fmt.Sprintf as fallback
			value = fmt.Sprintf("%v", field.Interface())
		}

		result[key] = value
	}

	return result
}

// generateInstanceKeyWithOrder creates instance key using hardcoded label order
// Format: {context}.{label_value1}_{label_value2}_...
// Label order is ALWAYS from the context definition, ensuring consistency
func generateInstanceKeyWithOrder(contextName string, labelKeys []string, labels map[string]string) string {
	if len(labelKeys) == 0 || len(labels) == 0 {
		return contextName
	}

	// Build label values in the EXACT order specified in context
	labelValues := make([]string, 0, len(labelKeys))
	for _, key := range labelKeys {
		if value, ok := labels[strings.ToLower(key)]; ok {
			labelValues = append(labelValues, cleanLabelValue(value))
		}
	}

	if len(labelValues) > 0 {
		return contextName + "." + strings.Join(labelValues, "_")
	}

	return contextName
}

func getContextMetadata(ctx interface{}) *ContextMetadata {
	// Extract metadata from Context[T] using reflection
	return extractContextMetadata(ctx)
}

// extractContextMetadata extracts metadata from a Context[T] pointer
func extractContextMetadata(ctx interface{}) *ContextMetadata {
	v := reflect.ValueOf(ctx)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return nil
	}

	// Create metadata object
	meta := &ContextMetadata{}

	// Extract fields
	if name := v.FieldByName("Name"); name.IsValid() && name.Kind() == reflect.String {
		meta.Name = name.String()
	}
	if family := v.FieldByName("Family"); family.IsValid() && family.Kind() == reflect.String {
		meta.Family = family.String()
	}
	if title := v.FieldByName("Title"); title.IsValid() && title.Kind() == reflect.String {
		meta.Title = title.String()
	}
	if units := v.FieldByName("Units"); units.IsValid() && units.Kind() == reflect.String {
		meta.Units = units.String()
	}
	if typ := v.FieldByName("Type"); typ.IsValid() {
		// Type is module.ChartType (string)
		if typ.Kind() == reflect.String {
			meta.Type = module.ChartType(typ.String())
		}
	}
	if priority := v.FieldByName("Priority"); priority.IsValid() && priority.Kind() == reflect.Int {
		meta.Priority = int(priority.Int())
	}
	if updateEvery := v.FieldByName("UpdateEvery"); updateEvery.IsValid() && updateEvery.Kind() == reflect.Int {
		meta.UpdateEvery = int(updateEvery.Int())
	}

	// Extract LabelKeys slice
	if labelKeys := v.FieldByName("LabelKeys"); labelKeys.IsValid() && labelKeys.Kind() == reflect.Slice {
		meta.HasLabels = labelKeys.Len() > 0
		for i := 0; i < labelKeys.Len(); i++ {
			if key := labelKeys.Index(i); key.Kind() == reflect.String {
				meta.LabelOrder = append(meta.LabelOrder, key.String())
			}
		}
	}

	// Extract dimensions slice
	if dims := v.FieldByName("Dimensions"); dims.IsValid() && dims.Kind() == reflect.Slice {
		for i := 0; i < dims.Len(); i++ {
			dim := dims.Index(i)
			if dim.Kind() == reflect.Struct {
				d := Dimension{}
				if name := dim.FieldByName("Name"); name.IsValid() && name.Kind() == reflect.String {
					d.Name = name.String()
				}
				if algo := dim.FieldByName("Algorithm"); algo.IsValid() {
					// Algorithm is module.DimAlgo type (string)
					if algo.Kind() == reflect.String {
						d.Algorithm = module.DimAlgo(algo.String())
					}
				}
				if mul := dim.FieldByName("Mul"); mul.IsValid() && mul.Kind() == reflect.Int {
					d.Mul = int(mul.Int())
				}
				if div := dim.FieldByName("Div"); div.IsValid() && div.Kind() == reflect.Int {
					d.Div = int(div.Int())
				}
				if precision := dim.FieldByName("Precision"); precision.IsValid() && precision.Kind() == reflect.Int {
					d.Precision = int(precision.Int())
				}
				meta.Dimensions = append(meta.Dimensions, d)
			}
		}
	}

	return meta
}

func getContextUpdateInterval(contextName string) int {
	// Get update interval for context
	// This will look up from registered contexts
	return 1
}

// Debugf logs a debug message (delegate to collector's logger)
func (s *CollectorState) Debugf(format string, args ...interface{}) {
	if s.collector != nil {
		s.collector.Debugf(format, args...)
	}
}

// Warningf logs a warning message (delegate to collector's logger)
func (s *CollectorState) Warningf(format string, args ...interface{}) {
	if s.collector != nil {
		s.collector.Warningf(format, args...)
	}
}

// Errorf logs an error message (delegate to collector's logger)
func (s *CollectorState) Errorf(format string, args ...interface{}) {
	if s.collector != nil {
		s.collector.Errorf(format, args...)
	}
}

// Infof logs an info message (delegate to collector's logger)
func (s *CollectorState) Infof(format string, args ...interface{}) {
	if s.collector != nil {
		s.collector.Infof(format, args...)
	}
}
